
import re, json

linker = '%'

def generate_js_protocol(name, description, settings, instructions, type):

    bin_filters = {}
    output_rules = []
    modify_rules = []
    options = {}
    
    quoted_re = re.compile(r'"[a-zA-Z0-9_ ]*"')
    varname_re = re.compile(r'([a-zA-Z]\w*)([ +\-*/)=]|$)')
    equals_re = re.compile(r'[^=]=[^=]')
    if_split_re = re.compile(r'[=<>]|<=|>=')
    def js_expr(expr, var_prefix='', var_func=''):
        expr = equals_re.sub('===', expr)
        if not quoted_re.match(expr) is None:
            return expr
        if var_prefix:
            if var_func:
                var_prefix = var_func+var_prefix
                expr = varname_re.sub(var_prefix + r'\1'+ ')' + r'\2', expr)
            else:
                expr = varname_re.sub(var_prefix + r'\1\2', expr)
        return expr

    settings = iter(settings.split('\n'))
    for line in settings:
        words = line.strip().split()
        if not words: continue
        
        if words[0].lower() == 'input':
            # Parse requirements and preferences for one bin.
            bin_name = ' '.join(words[1:-1])
            line = next(settings)
            while line.find('}') == -1:
                words = line.strip().split()
                if words[0].lower() == 'prefer':
                    expr = js_expr(' '.join(words[1:]), 'item.', 'si(')
                else:
                    expr = js_expr(line.strip(), 'item.', 'si(')
                    bin_filters.setdefault(bin_name, []).append(expr)
                bin_filters.setdefault('~'+bin_name, []).append(expr)
                line = next(settings)

        elif words[0].lower() == 'output':
            # Parse rules for one output type.
            #If we have normal protocol
            if(type == 'Protocol'):
                input_bin = ' '.join(words[1:-1]).split('->')[0].strip()
                output_bin = ' '.join(words[1:-1]).split('->')[1].strip()
            
                rule = '''if (!protocol_items['add'].hasOwnProperty('%s')) { ''' % output_bin
                rule += '''protocol_items['add']['%s'] = []; } ''' % output_bin
                rule += '''$.each(selected_items['%s'], function() { ''' % input_bin
                rule += '''var item = {}; '''
                line = next(settings)
                while line.find('}') == -1:
                    words = line.split(':')
                    if words[1].strip().lower() == 'link':
                        rule += '''item['%s'] = this.id; ''' % (linker+words[0].strip())
                    else:
                        expr = js_expr(words[1].strip(), 'this.', 'si(')
                        rule += '''item['%s'] = %s; ''' % (words[0].strip(), expr)
                    line = next(settings)
                rule += '''protocol_items['add']['%s'].push(item); });''' % output_bin
                output_rules.append(rule)

            #Different for adding items through add item menu
            else:
                if len(' '.join(words[1:-1]).split('->')) > 1:
                    output_bin = ' '.join(words[1:-1]).split('->')[1].strip()
                else:
                    output_bin = ' '.join(words[1:-1]).split('->')[0].strip()

                rule = '''if(typeof(valid_item_types['%s']) !== "object"){valid_item_types['%s'] = {};}''' % (name,name)
                #Bool to check that item type is set
                type_set = False
                line = next(settings)
                while line.find('}') == -1:
                    parts = line.split(':')
                    parts = [p.strip() for p in parts]
                    
                    if parts[1] == '' or parts[1] == ' ':
                        parts[1] = '''""'''
                   
                    if parts[0][0] == "%" and (parts[1] == '""' or parts[1] == '" "'):
                        parts[1] = '''"Linker attribute"'''
                    if parts[1].count('"') < 2:
                        parts[1] = '"' + parts[1].strip() + '"'
                    if parts[0].strip() == 'Type' or parts[0].strip() == 'type':
                        type_set = True

                    rule += '''valid_item_types['%s']['%s'] = %s;''' % (name, output_bin+" > "+parts[0].strip(), parts[1].strip())
                    line = next(settings)

                if not type_set:
                    rule += '''valid_item_types['%s']['%s > Type'] = "%s";''' % (name, output_bin, output_bin)
                
                output_rules.append(rule)

        elif words[0].lower() == 'modify':
            bin_name = ' '.join(words[1:-1])
            rule = "protocol_items['mod']['%s'] = [];" % bin_name
            rule += "$.each(selected_items['%s'], function() { " % bin_name
            rule += "var item = {id: this.id}; "
            line = next(settings)
            while line.find('}') == -1:
                words = line.split(':')
                expr = js_expr(words[1].strip(), 'this.', 'si(')
                rule += "item['%s'] = %s; " % (words[0].strip(), expr)
                line = next(settings)
            rule += "protocol_items['mod']['%s'].push(item); }); " % bin_name
            modify_rules.append(rule)

        elif words[0].lower() == 'settings':
            line = next(settings)
            while line.find('}') == -1:
                words = line.strip().split(':')
                if words[1].find('[') != -1 and words[1].find(']') != -1:
                    options[words[0].strip().lower().replace(' ', '_')] = {'type': 'radio', 'range': words[1].strip()}
                elif words[1].find('true') != -1:
                    options[words[0].strip().lower().replace(' ', '_')] = {'type': 'checkbox', 'range': words[1].strip()}
                else:
                    options[words[0].strip().lower().replace(' ', '_')] = {'type': 'slider', 'range': words[1].strip()}
                line = next(settings)

    options = json.dumps(options)
    
    # Check for perfect bin matches first.
    bin_filters_perfect = [(bin, rules)
        for bin, rules in bin_filters.items() if bin[0] != '~']
    bin_filters_poor = [(bin, rules)
        for bin, rules in bin_filters.items() if bin[0] == '~']
    bin_filters = bin_filters_perfect + bin_filters_poor
    
    input_bins = ['"%s"' % bin[0] for bin in bin_filters_perfect]
    
    begin = [m.start() for m in re.finditer('{', instructions)]
    close = [m.start() for m in re.finditer('}', instructions)]

    table_hash = {}
    instr_func = ''
    
    if len(begin) != len(close):
        return 'ERROR: Amount of { and } does not match.'
    
    if not bool(begin):
        instr_func = '''out += "%s" ;''' % instructions[0:]
    else:
        instr_func += '''out += "%s" ;''' % instructions[0:begin[0]].replace("\n", "<br>")
        if_end = [0, 0]
        table = False

        for idx, val in enumerate(begin):
            substr = instructions[begin[idx] + 1:close[idx]].strip()
            if substr.find('if') != -1:
                substr = re.sub(r'if\(','',substr[:-1]).strip()
                operator = if_split_re.findall(substr)
                if len(operator):
                    operator = operator[0].replace('=','==')
                else:
                    operator = ''
                words = if_split_re.split(substr)
                words[0] = js_expr(words[0],'options.')
                if len(words) > 1:
                    words[1] = js_expr(words[1])
                else:
                    words.append('')
                instr_func += "if(" + words[0] + operator + words[1] + '){'
                if_end[0] += 1
            elif substr.find('end') != -1:
                if not table:
                    instr_func += '}'
                table = False
                if_end[1] += 1
            elif substr.find('table') != -1:
                bin = ' '.join(substr.split(' ')[1:])
                table_hash[bin] = {}
                table_str = instructions[close[idx] + 1:begin[idx + 1]].strip()
                instr_func += '''protocol_items['%s'] = [];''' % bin.replace(' ','_')
                instr_func += '''$.each(selected_items["%s"], function(index, value){''' % bin.replace(' ', '_')
                instr_func += '''var table_item = {};'''
                for line in table_str.split('\n'):
                    words = line.split(':')
                    words[1] = js_expr(words[1], 'this.')
                    instr_func += '''table_item['%s'] = %s;''' % (words[0], words[1])
                
                instr_func += '''protocol_items['%s'].push(table_item); ''' % bin.replace(' ', '_')
                instr_func += '''}); '''
                if_end[0] += 1
                table = True
            elif not re.search(r"\w+\(.+\)", substr) is None:
                instr_func += '''out += %s;''' % substr.strip().replace("\n", "<br>")

            if idx + 1 < len(begin) and not table:
                instr_func += '''out += "%s";''' % instructions[close[idx] + 1:begin[idx + 1]].replace("\n", "<br>")

        if if_end[0] != if_end[1]:
            return "ERROR: Amount of if and end doesn't match."

        instr_func += '''out += "%s";''' % instructions[close.pop() + 1:].replace("\n", "<br>")
            

    return '''
        name: '%s',
        description: '%s',
        output: function() {
            protocol_items['add'] = {};
            %s
        },
        input: function() {
            protocol_items['mod'] = {};
            %s
        },
        input_bins: [%s],
        selector: function(item) {
            %s
            return '';
        },
        settings: %s,
        instructions: function() {
            var out = '';
            %s
            console.log(options);
            return out;
        }
    ''' % (name, description, '\n'.join(output_rules),
        '\n'.join(modify_rules), ', '.join(input_bins),
        '\n'.join(["if ((%s)) return '%s';" % (') && ('.join(rules), bin)
        for bin, rules in bin_filters]), options, instr_func)



def generate_js_addItem(name, settings):
    
    attrib = "{"
    settings = iter(settings.split('\n'))
    for line in settings:
        parts = line.split(':')
        print(parts)
        if parts[1] == '' or parts[1] == ' ':
            parts[1] = '''""'''
        print(parts)
        if parts[1].count('"') < 2:
            parts[1] = '"' + parts[1].strip() + '"'
        attrib += '''"%s":%s,''' % (parts[0].strip(), parts[1].strip())

    attrib = attrib[:-1]
    attrib += "}"
    print (attrib)
    return '''
        name: '%s',
        description: '%s'
        ''' % (name, attrib)


patient_to_tissue_example = {
    'name': 'Patient -> Tissue (custom)',
    'authors': 'Visakorpi lab',
    'description': '''This protocol creates tissue samples that are linked to patients.''',
    'settings': '''
input Patient {
    Type = 'Patient'
}
output Patient -> Tissue {
    Type: "Tissue"
    Patient: link
    Location: Location
}
modify Patient {
    Location: "Somewhere else"
}
''',
    'instructions': '''This protocol creates tissue samples that are linked with patients.'''
}

tissue_to_dna_extract_example = {
    'name': 'Tissue -> DNA extract (custom)',
    'authors': 'Visakorpi lab',
    'description': '''This protocol creates DNA extracts that are linked with tissue samples.''',
    'settings': '''
input Tissue {
    Type = 'Tissue'
}
output Tissue -> DNA extract {
    Type: "DNA extract"
    Tissue: link
    Quantity: volume(Quantity/2, 'ug')
}
modify Tissue {
    Quantity: volume(Quantity*0.5, 'ug')
}
''',
    'instructions': '''This protocol creates DNA extracts that are linked with tissue samples.'''
}

table_to_dna_extract_example = {
    'name': 'Table -> DNA extract (custom)',
    'authors': 'Visakorpi lab',
    'description': '''This protocol creates DNA extracts that are linked with tissue samples.''',
    'settings': '''
input Tissue {
    Type = 'Tissue'
}
input Patient {
    Type = 'Patient'
}
output Tissue -> DNA extract {
    Type: "DNA extract"
    Tissue: link
}
''',
    'instructions': '''This protocol creates DNA extracts that are linked with tissue samples.
    {table Tissue}
    Trizol: volume(3*Quantity)
    Water: volume(5*Quantity)
    {end}{table Patient}
    Trizol: Location+' qd12d'
    Water: Attrib1+' d1ed'
    {end} '''
}

long_protocol_example = {
    'name': 'Long protocol',
    'authors': 'Visakorpi lab',
    'description': '''This protocol is example of protocol instruction functions''',
    'settings': '''
input Tissue {
    Type = 'Tissue'
}
input Patient {
    Type = 'Patient'
}
output Tissue -> DNA extract {
    Type: "DNA extract"
    Tissue: link
}
settings {
volume: 13-65
Kit: ['Qiagen DNA extraction kit', 'Illumina miRNA extraction kit']
phase X: true
water: 5-167
}
''',
    'instructions':'''1.Homogenize the frozen tissue sections immediately in TRI Reagent {Slider("volume", "tri")}ml (1ml per 50-100mg tissue) using syringe and 20G-needle. Use 1ml TRI-reagent for 10x20uM sections
            Notice! Handle only one sample at a time before homogenization and hold all samples before homogenization at -70C metal block or in dry ice.
            2. Lysate cells in TRI-reagent by passing the lysate through a 20-gauge (0.9 mm) needle attached to a sterile plastic syringe at least 5-10 times or until a homogeneous lysate is achieved!
            3.Incubate for 5 mins at room temp.
            4.Add {volumeMultiply('tri','etanol',200)}ul chloroform
            5.Shake vigorously 15 secs
            {if(selected('Tissue') > 5)}
            6.Incubate for 3 mins at room temp
            {end}
            7.Centrifuge at 12,000g for 15 mins at 4oC
            {if(kit = 'Illumina miRNA extraction kit')}
            8.Transfer aqueous (RNA) phase (about 60% of the volume of TR reagent used for homogenization) to a fresh tube -> RNA continue below
            9.Keep remainder on ice (or store in -70C) for later DNA/protein isolation if needed ->DNA isolation
            {end}
            10.Add 12 ml isopropanol and 1ul (if very small sample) of glycogen. Mix well by turning tubes several times (5-10) up and down.
            {if(phase_x)}
            11.Incubate 10 mins at room temp.
            {if(water < 12)}
            12.Spin at 12000g for 10 mins at 4oC
            {end}
            {end}
            13.Remove the supernatant carefully by pipeting (do not waste any parts of the pellet!)
            14.Wash RNA pellet with {volumeMultiply('etanol')*10}ml 75% ethanol (in RNAse free water) (you may stop and store here at 4oC)
            15.Centrifuge at 8000g for 5 mins at 4oC
            16.Carefully remove all supernatant by pipetting (Don't miss the pellet!)
            17.Air-dry pellet for 3-5 mins (as long as all ethanol has evaporated)
            18.Dissolve RNA in 30-50ul (1ml TRI-reagent)
            19.Quantitate RNA (nano-drop)
            20.Store RNA in -70C'''
}


